﻿namespace Arms.Persistence
{
    using Arms.Persistence.Resources;
    using Arms.Domain;
    using Arms.Domain.Specifications;
    using Arms.Framework.Validators;
    using System;
    using System.Collections.Generic;
    using System.Data.Entity;
    using System.Linq;
    using System.Linq.Expressions;

    /// <summary>
    /// Base class for all repositories
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class Repository<T> : IRepository<T> where T : Entity
    {
        private readonly IDbContext _context;
        private DbSet<T> _set;

        public Repository(IDbContext context)
        {
            _context = Validation.Ensure(context);
            _set = context.Set<T>();
        }

        public IQueryable<T> Query()
        {
            return _set;
        }
        /// <summary>
        /// Gets a collection of entities matching criteria that are in active state.
        /// </summary>
        /// <param name="filter"></param>
        /// <returns></returns>
        public IEnumerable<T> Get(Expression<Func<T, bool>> filter = null)
        {
            IQueryable<T> query = _set;

            if (filter != null)
            {
                query = query.Where(filter);
            }

            return query.Where(x => x.ActiveState == EntityActiveState.Active);
        }
        public IEnumerable<T> GetAll(Expression<Func<T, bool>> filter = null)
        {
            IQueryable<T> query = _set;

            if (filter != null)
            {
                query = query.Where(filter);
            }

            return query;
        }
        /// <summary>
        /// Gets a collection of entities matching specification.
        /// </summary>
        /// <param name="specification"></param>
        /// <returns></returns>
        public IEnumerable<T> GetBySpecification(ISpecification<T> specification)
        {
            return this.Get(specification.SatisfiedBy());
        }
        /// <summary>
        /// Finds an entity by its identity.
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public T Find(int id)
        {
            return _set.Find(id);
        }
        /// <summary>
        /// Finds an entity that matches criteria.
        /// </summary>
        /// <param name="filter"></param>
        /// <returns></returns>
        public T Single(Func<T, bool> filter)
        {
            return _set.SingleOrDefault(filter);
        }
        /// <summary>
        /// Finds the first entity that matches criteria.
        /// </summary>
        /// <param name="filter"></param>
        /// <returns></returns>
        public T First(Func<T, bool> filter = null)
        {
            return filter != null ? _set.FirstOrDefault(filter) : _set.FirstOrDefault();
        }
        /// <summary>
        /// Finds the last entity that matches criteria.
        /// </summary>
        /// <param name="filter"></param>
        /// <returns></returns>
        public T Last(Func<T, bool> filter = null)
        {
            return filter != null ? _set.LastOrDefault(filter) : _set.LastOrDefault();
        }
        /// <summary>
        /// Determines whether there are any entities matching the criteria.
        /// </summary>
        /// <param name="filter"></param>
        /// <returns></returns>
        public bool Any(Func<T, bool> filter = null)
        {
            return filter != null ? _set.Any(filter) : _set.Any();
        }
        /// <summary>
        /// Adds an entity.
        /// </summary>
        /// <param name="entity"></param>
        public void Insert(T entity)
        {
            Validation.Ensure(entity, Errors.InsertNullEntity);

            _set.Add(entity);
            _context.Entry(entity).State = EntityState.Added;
        }
        /// <summary>
        /// Updates and tracks entity.
        /// </summary>
        /// <param name="entity"></param>
        public void Update(T entity)
        {
            Validation.Ensure(entity, Errors.UpdateNullEntity);

            _set.Attach(entity);
            _context.Entry(entity).State = EntityState.Modified;
        }
        /// <summary>
        /// Changes the state of the entity.
        /// </summary>
        /// <param name="entity"></param>
        /// <param name="state"></param>
        public void SetState(T entity, EntityActiveState state)
        {
            entity.ActiveState = state;
            this.Update(entity);
        }
    }
}
